Clojure 이해하기
처음 자바스크립트를 접할 때 가장 어렵다고들 하는 Clojure(이하 클로저)에 대해 이야기하고자 합니다.
클로저란?
클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다'. (MDN발췌)
Lisp에서 시작되었으며, 함수형 프로그래밍에 중점을 둔 범용 프로그래밍 언어입니다.(wiki)
처음 자바스크립트를 접할 때 가장 어렵다고들 하는 Clojure(이하 클로저)에 대해 이야기하고자 합니다.
클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다'. (MDN발췌)
Lisp에서 시작되었으며, 함수형 프로그래밍에 중점을 둔 범용 프로그래밍 언어입니다.(wiki)
wiki 에서 인용한 것을 보면 클로저는 자바스크립트만의 특징적인 개념이 아닌 오래전부터 다른 진영에서 존재해왔던 개념입니다.
자바스크립트가 유행하고 클로저 기술이 아주 많이 사용 되다보니 오해를 하고 있는 사람들이 많은 것 같습니다.
개념
개발자답게 코드로 이야기해보겠습니다.
function a(){
var outerV = 1;
return function(){
var innerV = 2;
var result = outerV + innerV;
console.log(result);
} // 내부 함수 (B)
}
var sum = a();
sum();
// 3 (결과가 이상하게 3이 나왔습니다. 왜그럴까요?)
a()
가 실행되면서 내부 함수 (B)
가 sum
반환 되었습니다. sum()
을 실행하니 결과는 3
이 출력되었습니다.
여기서 의문점을 가져야하는 부분은 sum
함수에 outerV
가 어떻게 1
로 할당 되었을까 입니다.
이는 앞선 포스팅 [JS] 스코프 체인 이란? 에서 유추 할 수 있습니다.(읽고오십시오.)
자바스크립트 함수는 생성 시점에 Execution Context 가 생성되고 평가 됩니다.
그래서 sum
이 생성되는 시점에 Scope Chain 에 의해 outerV
을 탐색하여 참조를 기억하게 됩니다.
그리하여 실행 시점에 outerV
는 알고 있게 되어 1+2
를 할 수 있게 되고, 상단에 MDN에서 발췌한 내용 클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다'
의 명제가 성립되어집니다.
이것이 바로 클로저 입니다.
좀더 개념
다른 예제를 살펴보겠습니다.
function count(){
var i=0;
for(i=0 ; i<5 ; i++){
setTimeout(function(){
console.log(i);
},1000);
}
}
count();
위 예제의 결과는 예상과 다르게 1,2,3,4,5
가 아닌 5,5,5,5,5
가 나오게 됩니다. 왜 그럴까요?
자바스크립트는 스코프체인에 의해 변수에 접근 할 때는 참조 방식으로 접근하게 됩니다.
자 그럼 코드를 분석해봅시다.
실제로는 for문
이 5번이 다돈 후에야 setTimeout
실행함수가 실행됩니다. (for문 5번도는건 순식간이라서..) 그러면 이미 i
는 5
가 되어있으며, i
를 참조하고 있는 setTimeout
실행 함수는 5
를 출력하게 되는 것 입니다.
그럼 이 문제를 해결 하기 위해선 어떻게 해야할까요?
이때 필요한 것이 클로저입니다.
클로저는 내부 함수를 둘러싼 외부 함수(환경)를 기억하고 있는다. 라고 하였습니다.
원하는 결과인 1,2,3,4,5
를 나오게 하기 위한 방법은?
function count(){
for(var i=0 ; i<5 ; i++){
(function(i){
setTimeout(function(){
console.log(i);
},1000);
})(i);
}
}
count();
//es6 방식
let count = () => {
for(let i=0 ; i<5 ; i++){
setTimeout(() => {
console.log(i);
},1000);
}
}
count();
ES5에서는 setTimeout
을 감싸는 즉시실행함수로 i
를 for문
이 돌때마다 각 상태를 즉시실행함수 안의 스코프에 기억하게 만들어버리면 됩니다.
ES6는 scope 방식부터가 달라져서 var->let
으로만 바꿔도 바로 됩니다. (이는 var
와 let
의 엄청난 차이가 있기 때문입니다만.,.생략..)
결론
클로저의 마법은 정말 놀랍습니다.
위 내용은 정말 기본적인 예제를 이용한 것이지만 실제로 저런 작은 코드들 때문에 문제를 발생하는 경우가 꽤 빈번합니다.
또한 클로저를 이용하여 고차함수를 구현 할수 있고, 커링(curring)의 기초가 될 수 있습니다.
클로저 화이팅
p.s. 면접 단골질문 중 하나는 클로저 입니다.